#pragma once

// Compile with -D_XOPEN_SOURCE to get limits.h to define IOV_MAX

#ifndef _XOPEN_SOURCE
    #define _XOPEN_SOURCE 700
#endif

#ifndef _LARGEFILE64_SOURCE
    #define _LARGEFILE64_SOURCE
#endif

#include <arpa/inet.h>
#include <dirent.h>
#include <emmintrin.h>
#include <execinfo.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <linux/capability.h>
#include <linux/falloc.h>
#include <linux/if_link.h>
#include <linux/limits.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/core_names.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/kdf.h>
#include <openssl/md5.h>
#include <openssl/params.h>
#include <openssl/opensslconf.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <openssl/ssl.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/file.h>
#include <sys/inotify.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/shm.h>
#include <sys/signalfd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/sysinfo.h>
#include <sys/timerfd.h>
#include <sys/times.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <unistd.h>

#include <stx/core/misc.hpp>

struct sockaddr;
struct sockaddr_in;
struct sockaddr_in6;

inline namespace stx
{
using Fd = int;

constexpr auto C_UNDEFINED                    = -4000;
constexpr auto C_IO_TIMEOUT                   = -5000;
constexpr auto C_IO_CANCELLED                 = -5001;
constexpr auto C_INVALID_FD                   = -5002;
constexpr auto C_INVALID_FD_STATE             = -5003;
constexpr auto C_INVALID_FSM_STATE            = -5004;
constexpr auto C_INVALID_INPUT_VIEW           = -5005;
constexpr auto C_INVALID_OUTPUT_SPAN          = -5006;
constexpr auto C_INVALID_LOCAL_EP             = -5007;
constexpr auto C_INVALID_REMOTE_EP            = -5008;
constexpr auto C_INVALID_HTTP_STATUS_LINE     = -5009;
constexpr auto C_INVALID_IP_ADDR              = -5010;
constexpr auto C_DNS_QUERY_ERROR              = -5011;
constexpr auto C_INVALID_URL                  = -5012;
constexpr auto C_INVALID_PORT                 = -5013;
constexpr auto C_RECV_HTTP_MSG_ERROR          = -5014;
constexpr auto C_PARSE_HTTP_STATUS_LINE_ERROR = -5015;
constexpr auto C_PEER_CLOSED                  = -5016;
constexpr auto C_INVALID_ARG                  = -5017;
constexpr auto C_USER_ERRNO_BASE_1            = -10000;
constexpr auto C_USER_ERRNO_BASE_2            = -20000;
constexpr auto C_USER_ERRNO_BASE_3            = -30000;
constexpr auto C_USER_ERRNO_BASE_4            = -40000;
constexpr auto C_USER_ERRNO_BASE_5            = -50000;

// Finite-State Machine State
enum class FsmState
{
    K_ACTIVE,
    K_FAILURE,
    K_SUCCESS,
};

void flushLogs() noexcept;

[[nodiscard]] fs::path const& getExePath();
[[nodiscard]] fs::path const& getExeDir();

[[nodiscard]] bool isReadable(Fd const fd) noexcept;
[[nodiscard]] bool isWritable(Fd const fd) noexcept;

DEFINE_SAFE_NUMBER(MaxLogBatchSize, std::size_t, C_PAGE_SIZE);
DEFINE_SAFE_DURATION(MaxLogFlushInterval, 5s);
DEFINE_SAFE_NUMBER(MaxLogBackupCount, std::size_t, 7);
DEFINE_SAFE_NUMBER(MaxLogFileSize, std::size_t, 2_Gi);

void createLogThread(fs::path                  fp_log,
                     MaxLogBatchSize const     max_batch_size     = {},
                     MaxLogFlushInterval const max_flush_interval = {},
                     MaxLogBackupCount const   max_log_backup_cnt = {},
                     MaxLogFileSize const      max_log_file_size  = {});

enum class FdState
{
    K_READABLE,
    K_WRITABLE,
    K_READABLE_OR_WRITABLE,
    K_SYS_ERROR,
    K_TIMEOUT,
};

[[nodiscard]] constexpr bool isReadable(FdState const fd_state) noexcept
{
    return FdState::K_READABLE == fd_state ||
           FdState::K_READABLE_OR_WRITABLE == fd_state;
}

[[nodiscard]] constexpr bool isWritable(FdState const fd_state) noexcept
{
    return FdState::K_WRITABLE == fd_state ||
           FdState::K_READABLE_OR_WRITABLE == fd_state;
}

[[nodiscard]] Future<FdState>
wait(Fd const fd, FdState const fd_state, Duration const dur_timeout);
[[nodiscard]] Future<FdState> wait(Fd const fd, FdState const fd_state);

[[nodiscard]] Future<std::uint32_t> watchFsEvents(fs::path const      fp,
                                                  std::uint32_t const mask);

[[nodiscard]] Future<std::ssize_t>
read(Fd const fd, MemSpan const ms, Duration const dur_timeout);
[[nodiscard]] Future<std::ssize_t> read(Fd const fd, MemSpan const ms);

[[nodiscard]] Future<std::ssize_t>
write(Fd const fd, MemView const mv, Duration const dur_timeout);
[[nodiscard]] Future<std::ssize_t> write(Fd const fd, MemView const mv);

[[nodiscard]] Future<std::ssize_t> read__(Fd const             fd,
                                          ::iovec const* const iov,
                                          std::size_t const    iov_size,
                                          Duration const       dur_timeout);

[[nodiscard]] inline Future<std::ssize_t>
read(Fd const fd, IsMemSpanRange auto const& mss, Duration const dur_timeout)
{
    auto io_vec = PodBuffer<::iovec>(Number(ranges::distance(mss)));
    auto i      = 0uz;

    for (auto const ms : mss)
    {
        io_vec[i].iov_base = std::bit_cast<void*>(ms.data());
        io_vec[i].iov_len  = Number(ms.size());
        ++i;
    }

    co_return co_await read__(fd, io_vec.data(), io_vec.size(), dur_timeout);
}

[[nodiscard]] inline Future<std::ssize_t> read(Fd const                   fd,
                                               IsMemSpanRange auto const& mss)
{
    co_return co_await read(fd, mss, 0ms);
}

[[nodiscard]] Future<std::ssize_t> write__(Fd const             fd,
                                           ::iovec const* const iov,
                                           std::size_t const    iov_size,
                                           Duration const       dur_timeout);

[[nodiscard]] inline Future<std::ssize_t>
write(Fd const fd, IsMemViewRange auto const& mvs, Duration const dur_timeout)
{
    auto io_vec = PodBuffer<::iovec>(Number(ranges::distance(mvs)));
    auto i      = 0uz;

    for (auto const mv : mvs)
    {
        io_vec[i].iov_base = std::bit_cast<void*>(mv.data());
        io_vec[i].iov_len  = Number(mv.size());
        ++i;
    }

    co_return co_await write__(fd, io_vec.data(), io_vec.size(), dur_timeout);
}

[[nodiscard]] inline Future<std::ssize_t> write(Fd const                   fd,
                                                IsMemViewRange auto const& mvs)
{
    co_return co_await write(fd, mvs, 0ms);
}

class FileDescriptor
{
protected:
    Fd fd_ = C_UNDEFINED;

public:
    virtual ~FileDescriptor();
    FileDescriptor() noexcept = default;
    explicit FileDescriptor(Fd const fd);

    NON_COPYABLE(FileDescriptor);

    DEFINE_MOVE_SEMANTICS(FileDescriptor, {
        this->close();
        this->fd_ = other.fd_;
        other.fd_ = C_UNDEFINED;
    });

    [[nodiscard]] explicit    operator bool() const noexcept;
    [[nodiscard]] std::size_t getHashCode() const noexcept;
    [[nodiscard]] Fd          fd() const noexcept;
    void                      close() noexcept;
};

class BiPipe : virtual public FileDescriptor
{
public:
    using FileDescriptor::FileDescriptor;

    [[nodiscard]] Future<std::ssize_t>
    read(MemSpan const ms, Duration const dur_timeout = 0ms) noexcept;
    [[nodiscard]] Future<std::ssize_t>
    write(MemView const mv, Duration const dur_timeout = 0ms) noexcept;

    [[nodiscard]] inline Future<std::ssize_t>
    read(IsMemSpanRange auto const& mss,
         Duration const             dur_timeout = 0ms) noexcept
    {
        return stx::read(this->fd_, mss, dur_timeout);
    }

    [[nodiscard]] inline Future<std::ssize_t>
    write(IsMemViewRange auto const& mvs,
          Duration const             dur_timeout = 0ms) noexcept
    {
        return stx::write(this->fd_, mvs, dur_timeout);
    }
};

class File final : public BiPipe
{
public:
    enum class OpenMode
    {
        K_READONLY,
        K_APPEND,
        K_MODIFY,
    };

    enum class Position
    {
        K_BEGIN,
        K_CURRENT,
        K_END,
    };

    using BiPipe::BiPipe;

    File(fs::path const& fp, OpenMode const open_mode) noexcept;
    File(fs::path const& fp,
         OpenMode const  open_mode,
         Position const  whence) noexcept;
    File(fs::path const&    fp,
         OpenMode const     open_mode,
         Position const     whence,
         std::ssize_t const offset) noexcept;

    std::ssize_t seek(Position const     whence,
                      std::ssize_t const offset) noexcept;
    std::ssize_t seek(Position const whence) noexcept;
    std::ssize_t seek(std::ssize_t const offset) noexcept;

    Status flush() noexcept;

private:
    OpenMode open_mode_ = {};
};

[[nodiscard]] Future<std::string> readFileToString(fs::path const& fp);
[[nodiscard]] Future<Buffer>      readFileToBuffer(fs::path const& fp);

[[nodiscard]] Future<std::ssize_t> appendTo(fs::path const fp,
                                            MemView const  mv);
[[nodiscard]] Future<std::ssize_t> writeTo(fs::path const fp, MemView const mv);

[[nodiscard]] inline Future<std::ssize_t>
appendTo(fs::path const fp, IsMemViewRange auto const& mvs)
{
    auto fout = File(fp, File::OpenMode::K_APPEND);
    co_return co_await fout.write(mvs, 0ms);
}

[[nodiscard]] inline Future<std::ssize_t>
writeTo(fs::path const fp, IsMemViewRange auto const& mvs)
{
    fs::remove(fp);
    if (fs::exists(fp))
    {
        co_return -EPERM;
    }

    co_return co_await appendTo(fp, mvs);
}

GLOBAL_VAR(gMaxTimeout, 10000h);

class TimerFd final : public FileDescriptor
{
public:
    TimerFd() noexcept = default;

    explicit TimerFd(Duration const dur) noexcept;

    TimerFd(Duration const dur_initial_expiration,
            Duration const dur_repeated_expiration) noexcept;

    void clear();

    bool reset(Duration const dur_initial_expiration,
               Duration const dur_repeated_expiration) noexcept;

    bool reset(Duration const dur_repeated_expiration) noexcept;
};

class EventFd final : public FileDescriptor
{
public:
    EventFd() noexcept;
    EventFd(std::size_t const init_value, bool const is_semaphore);
    explicit EventFd(std::size_t const init_value);

    /*
    0 == internal_counter:
    ----------------------
    return -1; and EAGAIN == errno.

    0 < internal_counter:
    ---------------------
    [Semaphore]:
	{
        --internal_counter;
        return 1;
	}
    
	[Non-semaphore]:
	{
        old_internal_counter = internal_counter;
        internal_counter     = 0;
        return old_internal_counter;
	}
	*/
    [[nodiscard]] std::uint64_t read(); // thread-safe

    void clear(); // thread-safe. call this->read() repeatedly until failure.

    // internal_counter += n;
    bool write(std::uint64_t const n); // thread-safe
    bool notify();                     // this->write(1);
};

template<IsNonVoid... Args_>
inline void createThread(std::string                      s_tag,
                         IsCallable<Args_ const&...> auto fn,
                         Args_ const&... args)
{
    auto const fn_thread =
        [MOVE(s_tag), MOVE(fn), DECAY_COPY_ARGS(args)] mutable
    {
        try
        {
            setOsThreadTag(s_tag.data());
            std::invoke(fn, args...);
        }
        catch (std::exception const& e)
        {
            LOG_ERROR("Thread exited: "sv, e.what());
        }
        catch (...)
        {
            LOG_ERROR("Thread exited abnormally."sv);
        }
    };

    std::thread(fn_thread).detach();
}

template<IsNonVoid... Args_>
inline void createThread(IsCallable<Args_ const&...> auto fn,
                         Args_ const&... args)
{
    createThread({}, std::move(fn), args...);
}

/*
Note: ::flock only works on multi-processes. It doesn't work on multi-threads.

int main()
{
    auto fmtx = FileMutex{};
    if (!fmtx.lock("/tmp/mtx_file"))
    {
        println("Failed to lock cross-process file mutex!"sv);
        return EXIT_FAILURE;
    }

    startMyProgram();
	return EXIT_SUCCESS;
}
*/
class FileMutex final : public FileDescriptor
{
public:
    ~FileMutex() final;
    FileMutex() noexcept = default;

    NON_COPYABLE(FileMutex);
    NON_MOVABLE(FileMutex);

    bool lock(fs::path const& fp_lock);
    void unlock();
};
} // namespace stx